/**
 * @author klv
 * refer: dataColumn.js
 */
var QS_tickerList=function(container,config,callback){
    this.grid=null;
    this.container=container;
    this.tValidation=null;
    this.callback=callback;
    this.sort_colnums=[];//sorted colnums
    this.holdings = [];  // save the holding when there is no Watchlist DataManager.
    this.config={
        subscribeInvisibleTickers:false,//subscribeInvisible tickers
        maxTicker:200,
        showInvalidTicker:false,
        realTime:true,
        width:600,
        height:600,
		needTickerValid:true,
        needStatic:true,
        viewKey:[{
            dataIndex: 'Symbol'
        },{
            dataIndex: 'Name'
        },{
            dataIndex: 'LastPrice'
        },{
            dataIndex:'Chg'
        },{
            dataIndex:'Chg%'
        },{
            dataIndex: 'Volume'
        }],
        displayLayout: 'list', //'heatmap',
        remove:true,
		autoWidth:false,
		autoHeight:false,
		headerContextMenu:true,
		rowDragable: true,
		rowDropable: true,
		sortable:true,
        cellResize:true,
        cellRearrange:true,
        highlightColumn:true,
        isFormat:false,
        isUnit:false,
        numberSeparate: "",//between number and unit
		limitMessage:''
    };
	this.staticValue={
		minWidth:200
	};
    $.extend(this.config,config);
    this.tickers=[];
    this.failedTickers=[];
	this.lastHeight=0;
	this.firstTag=true;
    this.visibleRows={};
    this.holdingId=-1;
    this.mapFairValueTxt = {
        "1": "Low",
        "2": "Medium",
        "3": "High",
        "4": "Very High",
        "5": "Extreme",
        "NA": ""
    };
    this.miniCols = [{ //for heatmap
        header: "Symbol",
        dataIndex: 'Symbol',
        dataType: 'string',
        className: 'cell-symbol',
		ellipsis:true
    }, {
        header: "Last",
        dataIndex: 'LastPrice',
        dataType: 'float'
    }, {
        header: "Chg%",
        dataIndex: 'Chg%',
        className: 'cell-chgp',
        dataType: 'float'
    }];
	if(this.config.headMapCols){
		this.miniCols=this.config.headMapCols;
	}
    this._textKey = document.body.textContent ? 'textContent' : 'innerText';
    this._init();
};
QS_tickerList.prototype={
    _init:function(){
        var html = '<div class="rtq-tickerlist rtq-wl"></div>';
        this.el = $(html).appendTo(this.container);
        this._createGrid(this.config.width,this.config.height,this.config.viewKey);
    },
	setTickerValidation:function(tickerValidation){
		this.tValidation = tickerValidation;
	},
    setWatchlistId:function(id){
        this.config.currentId = id;
    },
    changeOutSize: function(w, h){
        var p = this.el;
        if (!w || !h) {
            this.grid.resize(Math.max(this.staticValue.minWidth,p.width()), this.config.autoWidth?null:p.height());
        }else{
            this.grid.resize(Math.max(w,this.staticValue.minWidth),this.config.autoHeight?null:h);
        }
        var self = this;
        this._cacheVisibleRows(true, function(){
            self.bindData();
        });
    },
    setDecimal:function(decimal){
        this.config.decimal=(decimal!=null&&!isNaN(decimal))?decimal:2;
    },
    _getTickerOnRow: function(row){
        var ticker = null;
        if (row&&row.data&&!row.data.InvalidTicker) {
            var data = gData[row.data.gkey];
            if(data){
                if (data.type == 'FUTURE') {
                    ticker = data.exch && data.ticker ? (data.exch + ':' + data.ticker):null;
                }else {
                    ticker = data["performanceId"]||null;
                }
            }
        }
        return ticker;
    },
    _createColModel: function(viewKey){//[{dataIndex:"lastprice",width:60},{dataIndex:"volume",width:80}]
        var dataCols = [];
        if (this.config.displayLayout == 'heatmap') { //|| this.displayLayout.indexOf('wrap') > -1){
            dataCols =  this.miniCols.slice();
        }else {
            var viewList=typeof viewKey=='undefined'?this.config.viewKey:viewKey;
			if(this.config.displayLayout.indexOf('group')!=-1){
				viewList=this._addGroupToColunm(viewKey);
			}
            viewList=this._checkConfigColnum(viewKey);
            for(var i=0,l= viewList.length;i<l;i++){
                if(QS_WatchlistColumns[viewList[i].dataIndex]){
                    dataCols.push($.extend(true,{},QS_WatchlistColumns[viewList[i].dataIndex],viewList[i]));
                }
            }
        }
        this._genSortColnums(dataCols);
		dataCols.push({
			header: 'gkey', //'Ticker' for old watchlist
			dataIndex: 'gkey',
			hide: true
		});
		dataCols.push({
			header: 'id',
			dataIndex: 'id',
			hide: true
		});
        return dataCols; //this.config.cols = 
    },
    _addGroupToColunm:function(viewKey){  // add in the group by column key.
        if (typeof this.config.groupBy != 'undefined'&&this.config.groupBy!="undefined"&&this.config.groupBy!=null) {  // old config save as a string "undefined"
            var isIncludeGroupField=false;
            for (var i = 0; i < viewKey.length; i++) {
                if (viewKey[i].dataIndex == this.config.groupBy) {
                    isIncludeGroupField = true;
                    break;
                }
            }
            if (!isIncludeGroupField) { // the groupby field is not in the columns.
                viewKey.push({
                    dataIndex: this.config.groupBy,
					hide:true
                });
            }
        }
        return viewKey;
    },
    _checkConfigColnum:function(viewKey){
        //check remove
        var offset = -1;
        for (var i = 0; i < viewKey.length; i++) {
            if(viewKey[i].dataIndex == "remove") {
                offset = i ;
                if (!this.config.remove ) {  // not include the "remove" column.
                    viewKey.splice(offset, 1);
                    i--;
                }
            }
        }
        if (this.config.remove && (offset==-1)) {
            viewKey.push({
                dataIndex: "remove"
            });
        }
        return viewKey;
    },
    _genSortColnums:function(colnums){
        this.sort_colnums=[];
		var coltemp;
        for(var i=0,l=colnums.length;i<l;i++){
			coltemp=colnums[i];
            if (coltemp&&!coltemp.hide&&coltemp.type!="remove" && coltemp.dataIndex != this.config.groupBy){
                this.sort_colnums.push({
                    txt: coltemp.header,
                    val: coltemp.dataIndex,
                    selectable: true,
                    type: "ratio"
                });
            }
        }
    },
    _createGrid: function(w,h,dataIndexs){
        var self = this;
        var cols = this._createColModel(dataIndexs);
        var config = {
            outSideHeader: this.config.displayLayout == 'heatmap' ? true : false,
            column: cols,
            indexColKey: 'id',
            width: w,
            height: h,
            rowHeight: 25,
            rowLayout: this.config.displayLayout == 'heatmap' ? 'grid' : 'list',
            rowDragable: this.config.rowDragable,
            rowDropable: this.config.rowDropable,
			sortable:this.config.sortable,
			cellResize:this.config.cellResize,
			cellRearrange:this.config.cellRearrange,
			highlightColumn:this.config.highlightColumn,
            rowDragDropGroup: 'gridDDGroup',
            sortDataType: 'float',
            headerContextMenu: this.config.headerContextMenu,
            groupAutoSort: true,
            groupDataIndex: this.config.displayLayout.indexOf('group') > -1 ? this.config.groupBy : null,
			autoWidth:this.config.autoWidth,
			autoHeight:this.config.autoHeight
        };
        var callbacks = {
            onRowRemove: function(id, row){
                self.removeTicker(id, row.data.gkey);
                if(typeof self.callback.onRowRemove=='function'){
                    self.callback.onRowRemove(id,row);
                }
            },
            onRowDragStart: function(e, proxy, row){
                if (row.data&&!row.data.InvalidTicker) { //disable forex and future   || gData[row.data.gkey].type == 3
                    proxy._ticker = self._getTickerOnRow(row);
                    proxy._secType=gData[row.data.gkey].type;
                    proxy.html(gData[row.data.gkey].exch + ':' + gData[row.data.gkey].ticker);
                    if(typeof self.callback.onRowDragStart=='function'){
                        self.callback.onRowDragStart(e,proxy,row);
                    }
                }else{
                    row.dragObj.stop();
                    return;
                }
            },
            onRowRearrange: function(row, insertbeforerow){
                if(typeof self.callback.onRowRearrange=='function'){
                    self.callback.onRowRearrange(row,insertbeforerow);
                }
            },
            onCellSort: function(cellkey, order){
                self._cacheVisibleRows();
                self.bindData(function(){
                    self._updateHoldingOrder(self.config.currentId);
                });
                if(typeof self.callback.onCellSort=='function'){
                    self.callback.onCellSort(cellkey,order);
                }
            },
            onCellRearrange:function(cellkey,index){
                if(typeof self.callback.onCellRearrange=='function'){
                    self.callback.onCellRearrange(cellkey,index);
                }
            },
            afterResizeCol:function(cellkey,w){
                if(typeof self.callback.afterResizeCol=='function'){
                    self.callback.afterResizeCol(cellkey,w);
                }
            },
            onCellRemove: function(cellkey){
                //remove the column in sortting menu
                for (var i = 0; i < self.sort_colnums.length; i++) {
                    if (self.sort_colnums[i].val == cellkey) {
                        self.sort_colnums.splice(i, 1);
                    }
                }
                if(typeof self.callback.onCellRemove=='function'){
                    self.callback.onCellRemove(cellkey);
                }
            },
            onRowDoubleClick: function(e, row){
                if (row.data.InvalidTicker) {
                    return;
                }
				if(typeof self.callback.onRowDoubleClick=='function'){
					self.callback.onRowDoubleClick(e, row);
				}
            },
            onRowContextmenu: function(e, row){
				if(typeof self.callback.onRowContextmenu=='function'){
					self.callback.onRowContextmenu(e,row);
				}
            },
            onRowSelected: function(e, row){
				if(typeof self.callback.onRowSelected=='function'){
					self.callback.onRowSelected(e,row);
				}
            },
            onScrollEnd: function(l, t){
                self._cacheVisibleRows(true, function(){
                    self.bindData();
                });
                if(typeof self.callback.onScrollEnd=='function'){
                    self.callback.onScrollEnd(l,t);
                }
            },
            afterRebindData: function(DOMMap, rows){
				if (self.firstTag) {
		            Util.DelayRun('onload.tickerlist', self.loadHandler, 100, self);
		        }
		        else {
		            Util.DelayRun('heightchange.tickerlist', self.heightChangeHandler, 100, self);
		        }
				if(typeof self.callback.afterRebindData=='function'){
                    self.callback.afterRebindData();
                }
            },
            afterGroupDrop:function(){
                self._updateHoldingOrder(self.config.currentId);
                if(typeof self.callback.afterGroupDrop=='function'){
                    self.callback.afterGroupDrop();
                }
            },
			onSortedbyCell:function(cellkey,lastorder,callback){
                 if(typeof self.callback.onSortedbyCell=='function')
                    self.callback.onSortedbyCell(cellkey,lastorder,callback);
            },
			onCellClick:function(e,cell,row){
				if(typeof self.callback.onCellClick=='function'){
                    self.callback.onCellClick(e,cell,row);
                }
			}
        };
        
        if (this.config.displayLayout.indexOf('wrap') > -1) {
            this.grid = new Util.MultiDataGrid(this.el, [], {
                columnSeparate: true,
                width: w,
                height: h,
                gRowsHeight: 25,
                gColumns: config.column,
                indexColKey: 'Ticker',
                gConfig: config,
                groupAutoSort: true,
				fixHeight:!this.config.autoHeight,
                groupDataIndex: this.config.groupBy
            }, callbacks);
        }else {
            this.grid = new Util.DataGrid(this.el, {}, config, callbacks);
        }
        
        //add heatmap style
        if (this.config.displayLayout == 'heatmap') {
            this.grid.el.addClass('wl-heatmap');
        }else {
            this.grid.el.removeClass('wl-heatmap');
        }
        //add M* sector icon style
        if (this.config.groupBy == 'la03z') {
            this.el.addClass('wl-mstar-sector');
        }else {
            this.el.removeClass('wl-mstar-sector');
        }
    },
	heightChangeHandler:function(){
		 if(this.config.autoHeight&&typeof this.callback.onHeightChange=='function'){
			var height=this.el.height(),width=this.el.width();
			if(this.lastHeight!=height){
				this.callback.onHeightChange({
					component: "tickerList",
					height: height,
					width : width
				});
				this.lastHeight=height;
			}
		}
	},
	loadHandler:function(){
		if(typeof this.callback.onLoad=='function'){
			this.callback.onLoad({
				component: "tickerList",
                height: this.el.height(),
				width:this.el.width(),
				minWidth : this.staticValue.minWidth
            });
            this.firstTag = false;
        }
	},
    sort:function(dataIndex,direction){ //direction :up:"ASC",down:"DESC"
        this.grid.sortByCell(dataIndex,direction);
    },
    _updateHoldingOrder: function(id){
        //reorder holdings
        if (typeof QS_watchlistDataManager != 'undefined') {
            var holdIds = this.grid.getIDs();
            var holds = [];
            for (var i = 0; i < holdIds.length; i++) {
                holds.push({
                    HldgId: holdIds[i],
                    Rank: i
                });
            }
            QS_watchlistDataManager.updateHoldings(id, holds);
        }
    },
    groupBy:function(dataIndex,callback){
        var self=this;
        if(this.config.subscribeInvisibleTickers){//subscribe all tickers
            if (dataIndex != null) {
                var gKeylist = [];//gKey
                var list = [];//PID
                for (var i in this.grid.rows) {
                    var temp = this.grid.rows[i].data;
                    if (temp.InvalidTicker) 
                        continue;
                    gKeylist.push(temp["gkey"]);
                    list.push(gData[temp["gkey"]]["performanceId"]);
                }
                if (list.length > 0) {
                    getStaticData(list, function(){
                        self._groupBy(dataIndex, callback);   
                    });
                }
            }
        }else{
             this._groupBy(dataIndex, callback);
        }
    },
    _groupBy:function(dataIndex,callback){
        if(!QS_WatchlistColumns[dataIndex]){  // not support this dataIndex.
            return false;
        }
        this.config.groupBy = dataIndex;
        this.grid.setGroup(dataIndex);
        var cols = this.grid.getColumnCfg();
        this.grid.setColumnCfg(this._createColModel(cols)); //refresh the column for grouping
        //add M* sector icon style
        if (this.config.groupBy == 'la03z') {
            this.el.addClass('wl-mstar-sector');
        }
        else {
            this.el.removeClass('wl-mstar-sector');
        }
        this.reLoadHoldings(false);
        if(typeof callback=='function'){
            callback();
        }
    },
    changeViewType:function(viewType){//list,listwrap,listgroup,list wrap&group,heatmap
        if(this.config.displayLayout!=viewType){
            this.config.displayLayout = viewType || 'list';
            var width = this.grid.container.width();
            var height = this.grid.container.height();
            this.empty();
            this.grid.el.remove();       
            this._createGrid(width, height, this.config.viewKey);
            this.reLoadHoldings(false);
        }    
    },
    empty:function(){
        this.grid.empty();
    },
    setColumns:function(cols){  
		var offset = -1,dataindex=null,isIncludeGroupField=false;
		for (var i = cols.length-1; i >=0; i--) {
            dataindex = cols[i].dataIndex;
			if(dataindex == "remove"){
				offset = i ;
			}else if(dataindex == this.config.groupBy) {
				isIncludeGroupField = true;
			}
			if(offset != -1&&isIncludeGroupField) break;
		}
				
        this.config.viewKey = cols;
		if(this.config.displayLayout=="heatmap"){
			cols=this.miniCols;
		}
		this._genSortColnums(cols);
        //col-delbtn
        if (this.config.remove&&(offset==-1)) {
			cols.push(QS_WatchlistColumns["remove"]);
		}else if(!this.config.remove&&(offset!=-1)){
            // not include the "remove" column.
            cols.splice(offset, 1);
        }
		cols.push({
			header: 'gkey', //'Ticker' for old watchlist
			dataIndex: 'gkey',
			hide: true
		});
		cols.push({
			header: 'id',
			dataIndex: 'id',
			hide: true
		});
		var gridCol=$.merge([],cols);
		//group by
		if(this.config.groupBy&&!isIncludeGroupField){
			gridCol.push($.extend(true,{hide:true},QS_WatchlistColumns[this.config.groupBy]));
		}
        this.grid.setColumnCfg(gridCol);
        this.reLoadHoldings(false);
    },
    getSortColumns:function(){
       return this.sort_colnums;
    },
	getColumnsView:function(){
		var colnums=this.getColumnCfg(),coltemp=null,returnvalue=[];
        for(var i=0,l=colnums.length;i<l;i++){
            coltemp=colnums[i];
            if (coltemp&&!coltemp.hide&&coltemp.type!="remove"){
                returnvalue.push({
                    dataIndex: coltemp.dataIndex,
                    width: coltemp.width
                });
            }
        }
		return returnvalue;
	},
    getColumnCfg:function(){
       return this.grid.getColumnCfg();
    },
	getIDs:function(){
        return this.grid.getIDs();
    },
    getlastSortCellKey:function(){
        return this.grid.lastSortCellKey;
    },
    getSortOrder:function(){
        return this.grid.lastSortOrder;
    },
    setColumnsByKeys:function(keys){ // [{dataIndex:"LastPrice",width:60},{...}]
        this.config.viewKey = keys;
        this.grid.setColumnCfg(this._createColModel(keys));
        this.reLoadHoldings(false);
    },
    removeTicker:function(holdingId, gkey){
        this._deleteHolding(holdingId);
        this.tValidation.unSubscribe([gkey], true);
        delete this.visibleRows[holdingId];
        this._cacheVisibleRows(true);
		Util.DelayRun('heightchange.tickerlist', this.heightChangeHandler, 100, this);
    },
    removeRow:function(rowID){
        this.grid.removeRow(rowID);
        this.removeTicker(rowID);
    },
    addTickers:function(tickers,showAlert){//only support QS tickers
        if(!this._checkTickerLength(null,true)){//when save as a new watchlist and maximum row counts is different, need prompt.  
            var self = this;
			if (this.config.needTickerValid) {
				this.tValidation.showAlert = true;
				var isSubscribeTickers = this.config.subscribeInvisibleTickers;
				this.tValidation.checkTickers(tickers, function(o, t){
					   self._validationCallback(o, t, false,[],showAlert);
                       self.tValidation.showAlert = false;
				}, isSubscribeTickers);
			}else{
                this._validationCallback(tickers,null,false,[],showAlert);
			}
        }
    },
    reLoadHoldings:function(needCheck){ // 
        var self = this;
        if (typeof QS_watchlistDataManager != 'undefined'&&typeof this.config.currentId!="undefined") {
            QS_watchlistDataManager.getHoldings(this.config.currentId, function(holdings){
                if (needCheck) {
                    self.reBindData(holdings);
                }
                else {
                    self._reBindTickers(self.gkeyList, self.failedTickers, holdings, false);
                }
            });
        }else{
            this.reBindData(this.holdings);
        }
        
    },
    reBindData: function(holdings){  // bind holdings to grid, it will check all the ticker with ticker validation.
        var self = this;
        if (holdings.length == 0 || !holdings) {
            this.visibleRows = {};
            this.grid.reBindData([]);//for trigger afterbinddata
        } else {
            //this.showLoading(true);
            var tickers = [], tickersMap = {}, ticker;
            for (var i = 0; i < holdings.length; i++) {
                ticker = holdings[i].SecId || holdings[i].Ticker;
                if (!tickersMap[ticker]) {
                    tickersMap[ticker] = true;
                    tickers.push(ticker);
                }
            }
			if (this.config.needTickerValid) {
				this.tValidation.checkTickers(tickers, function(o, t){
                    if (t && t.length > 0) {
                        self._handleInvalidTicker(o, t, holdings);
                    }
                    else {
    					self._validationCallback(o, t, true, holdings);
                    }
				}, false);
			}else{
				this._validationCallback(tickers, t, true, holdings);
			}
        }
    },
    setSortICON:function(cols,sort){
        this.grid.setSortICON(cols,sort);
    },
    _handleInvalidTicker: function(gkeyList, invalidList, holdings){
        var self = this, invalidMap = {};
        //      TickerInValid='"{0}" {1} not covered now. Do you want to remove {2} from your Watchlist?<br/><br/>Click "Yes"-Remove {2}<br/>Click "No"-Keep {2} in your Watchlist';
        //        for(var i=0,l=t.length;i<l;i++){// remove reduplicative  invalid Ticker
        //            if(!invalidMap[t[i]]){
        //                invalidMap[t[i]]=true;
        //                invalidList.push(t[i]);
        //            }
        //        }
        var holdIds = [], ticker, tickerName, tickerNames = [], holds = [];
        for (var i = 0, l = holdings.length; i < l; i++) {
            ticker = holdings[i].SecId || holdings[i].Ticker;
            tickerName = holdings[i].Ticker || holdings[i].SecId;
            if ($.inArray(ticker, invalidList) > -1 && !holdings[i].Invalid) {
                holdIds.push(holdings[i].HldgId);
                holds.push({
                    HldgId: holdings[i].HldgId,
                    Invalid: true
                });
                if ($.inArray(tickerName, tickerNames) < 0) {
                    tickerNames.push(tickerName);
                }
            }
        }
        if (holdIds.length == 0) {
            self._validationCallback(gkeyList, invalidList, true, holdings);
        }
        else {
            var msg = String.format(QS_Lang.text["tickerinvalid1"], tickerNames.join('; '));
            Util.confirm(String.format("{0}<br/><br/><p style=\"padding-bottom:1px;\"><span style=\"display:inline-block;padding:2px;\">{1}</span></p><p style=\"padding-top:1px;\"><span style=\"display:inline-block;padding:2px;\">{2}</span></p>",
                msg,QS_Lang.text["tickerinvalid2"],QS_Lang.text["tickerinvalid3"]), function(b){
                if (b) {
                    QS_watchlistDataManager.deleteHoldings(self.config.currentId, holdIds);
                    invalidList = [];
                }
                else {
                    QS_watchlistDataManager.updateHoldings(self.config.currentId, holds);//remark the falg into pms as the ticker field.
                }
                self._validationCallback(gkeyList, invalidList, true, holdings);
            }, {
                okText: QS_Lang.watchlist["promptbyes"],
                cancelText: QS_Lang.watchlist["promptbno"],
                closeCallback: function(){
                    self._validationCallback(gkeyList, invalidList, true, holdings);
                }
            })
        }
    },
    _validationCallback: function(gkeyList,failedTickers,isRebind,holdings,showAlert){ // bind gData to watchlist.
        if (this.config.displayLayout.indexOf('group') != -1) {//if there has group, we will get all static data.
            var list = [];
            for (var i = 0; i < gkeyList.length; i++) {
                if (gData[gkeyList[i]]) {
                    list.push(gData[gkeyList[i]]["performanceId"]);
                }
            }
            var self=this;
            if (list.length > 0) {
                getStaticData(list, function(){
                    if (isRebind) {
                        self._reBindTickers(gkeyList,failedTickers,holdings,showAlert);
                    }else {
                        self._addTickers(gkeyList,showAlert);
                    }
                });
            }
        }else {
            if (isRebind) {
                 this._reBindTickers(gkeyList, failedTickers,holdings,showAlert);   
            } else {
                 this._addTickers(gkeyList, showAlert);
            }
        }
    },
    _addTickers:function(gkeyList,showAlert){//don't need to process failedTickers
        var maxTicker = 0,tickerLength=gkeyList.length,maxLimit=this._getMaxLimit();
		if (tickerLength > maxLimit&&showAlert) {
			if(!QS_watchlistDataManager||QS_watchlistDataManager.getSourceById(this.config.currentId)!='temp'){
				Util.alert(String.format(this.config.limitMessage || QS_Lang.text["watchlistlimit"], maxLimit));
			}else{
				Util.alert(String.format(QS_Lang.text["watchlisttemplimit"], maxLimit));
			}
		}
		maxTicker = Math.min(tickerLength,maxLimit);
        for (var i = 0; i < maxTicker; i++) {
            var hld = {
                SecId: gData[gkeyList[i]].performanceId,
                gkey: gkeyList[i],
                Ticker: this.config.isDotComUser ? gData[gkeyList[i]].Name : gData[gkeyList[i]].ticker,
                PurchasePrice: GB_DataManager.getMarketQuoteData(gkeyList[i], "LastPrice").value || '1'
            }
            var holdingId = this._insertHoldings([hld]);  
            var d = {
                'HldgId':holdingId,//for rebindticker
                'id': holdingId,
                'gkey': gkeyList[i],
                'pid': gData[gkeyList[i]].performanceId,
                'Symbol': gData[gkeyList[i]].ticker,
                'SecId':gData[gkeyList[i]].secId
            };
            //add grouping data
            if (this.config.groupBy) {
                d[this.config.groupBy] = this._getGroupData(gkeyList[i],holdingId);
            }
            this.grid.addRow(d, holdingId, 0);
            if (typeof QS_watchlistDataManager != 'undefined') {
                QS_watchlistDataManager.createHoldingMap(this.config.currentId);
            }
        } 
        this._cacheGkeyList();
        if (!this.config.subscribeInvisibleTickers) {
            this._cacheVisibleRows();
            this.bindData(); //get data from gData for visible rows.
        }else{
             for(var i=0;i<gkeyList.length;i++){
                this.bindDataRow(gkeyList[i],true);
            }
        }
        if (this.firstTag) {
			Util.DelayRun('onload.tickerlist', this.loadHandler, 100, this);
		}
		else {
			Util.DelayRun('heightchange.tickerlist', this.heightChangeHandler, 100, this);
		}
    },
	_getGroupData:function(gkey,holdingId){
		var returnValue="";
		if (gData[gkey][this.config.groupBy]) {
		    returnValue = gData[gkey][this.config.groupBy];
		}else {
		    var holdingData = this._getHoldingByHoldId(this.config.currentId, holdingId);
		    returnValue = holdingData ? holdingData[this.config.groupBy] : '';
		}
		return returnValue;
	},
	_getMaxLimit:function(){
		var maxCount = this.config.maxTicker;
		if (typeof QS_watchlistDataManager != 'undefined') {
			maxCount = QS_watchlistDataManager.getMaxTickers(this.config.currentId);
		}
		return maxCount;
	},
    _checkTickerLength:function(rowLength,showAlert){
        var flag = false;
        var rowCount=(typeof rowLength=='undefined'||rowLength==null)?this._getTickerLength():rowLength;
		var maxCount=this._getMaxLimit();
        if (rowCount >= maxCount) {
            if(!QS_watchlistDataManager||QS_watchlistDataManager.getSourceById(this.config.currentId)!='temp'){
				Util.alert(String.format(this.config.limitMessage || QS_Lang.text["watchlistlimit"], maxCount));
			}else{
				Util.alert(String.format(QS_Lang.text["watchlisttemplimit"], maxCount));
            }
            flag = true;
        }
        return flag;
    },
    _getTickerLength: function(){
        return this.grid.getRowLength();
    },
    bindDataRow:function(gKey,needFlash){  // bind the data from gData. if there are muil-rows has same ticker, it will update all rows.
        var aData = {};
        var cols = this.grid.getColumnCfg(), field;
        for (var c in cols) {
            field = cols[c].dataIndex;
            if (field == "Up/Down") 
                field = "Chg";
            aData[field] = {
                dataType: cols[c].dataType,
                value: gData[gKey] == null ? null : push.proxy.getMarketQuoteData(gKey, field).value
            };
        }
        this.updateData(gKey, aData, needFlash);
    },
    bindData:function(callback){  // bind data from gData to data grid. not need to check the ticker in ID service.
        var self=this,pidList=[],rows=[];
        if(this.config.subscribeInvisibleTickers){  // subscribe all tickers in grid
            rows = this.grid.rows;
        }else{
            rows = this.visibleRows;
        }
        
        if(!this.config.needStatic){
             for (var i in rows) {
                if(typeof(rows[i].data)=='undefined'||typeof(rows[i].data.gkey)=='undefined')
                    continue
                self.bindDataRow(rows[i].data.gkey,false);
            }
            if(typeof callback=='function'){
                callback();
            }
        }else{
            for(var i in rows){
                pidList.push(rows[i].data.SecId);
            }
            getStaticData(pidList, function(o){
                for (var i in rows) {
    				if(typeof(rows[i].data)=='undefined'||typeof(rows[i].data.gkey)=='undefined')
    					continue
                    self.bindDataRow(rows[i].data.gkey,false);
                }
                if(typeof callback=='function'){
                    callback();
                }
            });
        }
    },
    updateData: function(gKey, aData, needFlash){  // this interface is provided to push client to invork.
        var holdings = this._getHoldIDByGkey(gKey);
        if (!holdings || !holdings.length) {
            return false;
        }
        for (var i = 0; i < holdings.length; i++) {
            if (this.visibleRows[holdings[i]]) { //dont update the hidden rows
                var type, gValue, oValue;
                for (var field in aData) {
                    if (!aData.hasOwnProperty(field)) {
                        continue;
                    }
                    type = aData[field].dataType;
                    gValue = aData[field].value;
                    oValue = aData[field].oValue;//include in data come from push system.
                    //for update some data come from no push system
                    if (field == 'NumOfShares' || field == 'Comm' || field == 'P/L' || field == 'P/Lpc' || field == 'MarketValue' || field == 'AccountName' || field == 'Household' || field == 'PurchaseDate' || field == 'PurchasePrice') {
                        gValue = '';
                    }
                    if (typeof gValue != 'undefined' && gValue != null) {
                        this._updateDOM(gKey, field, gValue, oValue, type, needFlash, holdings[i]);
                    }
                }
            }
        }
    },
    
    _getHoldingByHoldId:function(currentid,hid){
        if (typeof QS_watchlistDataManager != 'undefined') {
            return QS_watchlistDataManager.getHoldingByHoldId(currentid, hid);
        }else{
            for(var i=0,l=this.holdings.length;i<l;i++){
                if(this.holdings[i].HldgId==hid){
                    return this.holdings[i];
                }
            }
            return {};
        }
    },
    _getHoldIDByGkey:function(gkey){
        var holdings = [];
        if (typeof QS_watchlistDataManager != 'undefined') {
            var holds = QS_watchlistDataManager.getHoldingMap(this.config.currentId);
            if (holds && typeof(holds[gkey])!="undefined") {  // holdings[gkey] may be -1.
                holdings = holds[gkey];
            }
        }else {
            for(var i=0,l=this.holdings.length;i<l;i++){
                if(this.holdings[i].gkey==gkey){
                    holdings.push(this.holdings[i].HldgId||this.holdings[i].id);
                }
            }
        }
        return holdings;
    },
    _deleteHolding:function(hid){
        if (typeof QS_watchlistDataManager != 'undefined') {
            QS_watchlistDataManager.deleteHoldings(this.config.currentId, [hid]);
        }else{
            for(var i=0,l=this.holdings.length;i<l;i++){
                if(this.holdings[i].HldgId==hid){
                    this.holdings.splice(i,1);
                }
            }
        }
    },
    _moveHolding:function(currentId, rowID, insertbeforerowId){
        if(typeof QS_watchlistDataManager != 'undefined') {
            QS_watchlistDataManager.moveHolding(currentId, rowID, insertbeforerowId);
        }else{
            
        }
    },
    _insertHoldings: function(holding){
        if (typeof QS_watchlistDataManager != 'undefined') {
            holdID=QS_watchlistDataManager.insertHoldings(this.config.currentId, holding)[0];
        }else{
            var holdID = this.holdingId--;
            var hld = holding[0];
            hld.HldgId = holdID;
            this.holdings.push(hld);   
        }
        return holdID;
    },
    clear:function(){
        for (var p in this) {
            if (!p || p == 'clear') {
                continue;
            }
            this[p] = null;
            delete this[p];
        }
    },
	_getAlertData: function(value){//change the status of alert
        var imgSrc = "";
        var scp = "javascript:if(typeof(workspaceManager)!='undefined') workspaceManager.openAlertSetting();";
        if (value == 1) {
            imgSrc = "IM/alarm1.gif";
        }
        else 
            if (value == 0) {
                imgSrc = "IM/alarm0.gif";
            }
            else {
                return "";
            }
        return String.format('<a  href="{0}"><img src="{1}"/></a>', scp, imgSrc);
    },
	_getColConfig:function(field){
		var cols=this.grid.getColumnCfg(),col={};
		for(var i=0,l=cols.length;i<l;i++){
			col=cols[i];
			if(field==col["dataIndex"]){
				return col;
			}
		}
	},
    _updateDOM: function(gKey,field,data,oldData,type,needFlash,holdingId){
        if (!this.grid.DOMMap[holdingId] || typeof data == 'undefined' ) {
            return;
        }
        var el = this.grid.DOMMap[holdingId][field],colConfig = QS_WatchlistColumns[field];
        if((!el&&field != "Chg")||(!el&&field == "Chg"&&!this.grid.DOMMap[holdingId]["Up/Down"])){ // if "chg" arrived it may be update the "up/down" field.
            return;
        }
        var _flashMethod = needFlash === false ? function(){}: flash,contentKey = this._textKey;
        var securitytype=gData[gKey]["type"];
        var defaultfloat=this.config.decimal,nubtag=(colConfig&&colConfig["dataType"]=="float")?true:false,ellipsis=false;
        switch (field) {
            case 'Volume':
                _flashMethod(el, null, null);
                data = data / 1000000;
                if(!this.config.decimal){
                    defaultfloat=4;//as default
                }
                break;
            case 'AskSize':
            case 'BidSize'://i
                _flashMethod(el, null, null);
                break;
            case 'AskPrice':
            case 'BidPrice':
            case 'Spread':
                _flashMethod(el, null, null);
                break;
            case 'Time':
                _flashMethod(el, null, null);
                data = FDate(data);//s
                break;
            case 'LastPrice':
                this._updatePortfolioDataDOM(gKey, holdingId);
                if (this.config.displayLayout != 'heatmap') {
                    _flashMethod(el, data, oldData);
                }
                break;
            case 'AfterTPrice':
                if (GB_DataManager.getMarketQuoteData(gKey, "showAfter").value == 0) {
                    data = '';//s
                }
                break;
            case 'Chg':
            case 'Chg%':
                //flash background for heatmap
                if (this.config.displayLayout == 'heatmap') {//s
                    if (el) {
                        var bg = '';
                        var L1 = 3;
                        var L2 = 2;
                        var L3 = 1;
                        var gColors = ['rtq-grid-cell-r1', 'rtq-grid-cell-r2', 'rtq-grid-cell-r3'];
                        var rColors = ['rtq-grid-cell-g1', 'rtq-grid-cell-g2', 'rtq-grid-cell-g3'];
                        if (data >= 0) {
                            if (data > L2) {
                                bg = rColors[0];
                            }
                            else 
                                if (data > L3 && data < L2) {
                                    bg = rColors[1];
                                }
                                else 
                                    if (data < L3) {
                                        bg = rColors[2];
                                    }
                        }
                        else {
                            if (data < -L2) {
                                bg = gColors[0];
                            }
                            else 
                                if (data < -L3 && data > -L2) {
                                    bg = gColors[1];
                                }
                                else 
                                    if (data > -L3) {
                                        bg = gColors[2];
                                    }
                        }
                        //this.grid.rows[gKey].el[0].style.background = bg;
                        var rowDOM = el.parentNode.parentNode;
                        rowDOM.className = rowDOM.className.replace(/rtq-grid-cell-(r|g)\d/g, '');
                        rowDOM.className += ' ' + bg;
                    }
                }
                else {
                    if (el) {
                        if (needFlash !== false) {
                            flashWithDirectional(el, data, oldData, data >= 0);
                        }
                        else {
                            el.style.color = data >= 0 ? QS_Style.greenFontColor : QS_Style.redFontColor;
                        }
                    }
                    if (field == "Chg") { // update the up/down column;
                        var upd = data;
                        if (upd == 0 || typeof upd == 'undefined') {
                            upd = '';
                        }
                        else {
                            var cls = upd > 0 ? "up" : "down";
                            upd = '<a class="updown ' + cls + '" href="javascript:void(0)">' + cls + '</a>';
                        }
                        var upEl = this.grid.DOMMap[holdingId]["Up/Down"];
                        if (upEl) 
                            upEl.innerHTML = upd;
                    }
                }
                break;
            case 'Alert':
                data = this._getAlertData(data);//s
                contentKey = 'innerHTML';
                break;
            case 'st200'://Mstar Rating
                var starStr = '';
                for (var i = 0; i < data; i++) {
                    starStr += '*';
                }
                data = '<span class="star' + data + '">' + starStr + '</span>';//s
                contentKey = 'innerHTML';
                break;
            case 'st201':// Mstar Fair Value Un
                data = this.mapFairValueTxt[data];//s
                break;
            case 'LastMarket':// Trade Market added by wind 20100413
                data = gExchangeMap[data];//s
                if (typeof(data) == 'undefined' || !data) {
                    data = ''
                }
                break;
            case 'PurchasePrice':
            case 'Comm':
                if(!this.config.decimal){
                    defaultfloat=5;//as default
                }
            case 'AccountName':
            case 'PurchaseDate':
            case 'Household':
                var v = this._getHoldingByHoldId(this.config.currentId, holdingId)[field];
                if (v) {
                    data = v;//s
                }
                else {
                    data = '';//s
                }
                break;
            case 'P/L':
            case 'P/Lpc':
            case 'MarketValue':
                data = this._calculatePL(gKey, holdingId)[field];//s
                break;
            case 'R/D'://s
                break;
			case 'performanceId':
				break;
            default:
                break;
        }
        //update data
		if (el) {
            if(nubtag){
                if(colConfig&&typeof colConfig["decimal"]!="undefined"){
                    defaultfloat=colConfig["decimal"];
                }
                data = formatDoubledata(data,securitytype,defaultfloat,this.config.isFormat,this.config.isUnit,this.config.numberSeparate);
            }
			ellipsis = colConfig?colConfig["ellipsis"]:false;
			if (!ellipsis) {
				if(colConfig&&colConfig["renderer"]){
					$(el).html(colConfig["renderer"](data));
				}else{
					el[contentKey] = data;
				}
	        }else{
	            this.grid.rows[holdingId].cells[field].updateCell(colConfig&&colConfig["renderer"]?colConfig["renderer"](data):data);
	        }
        }
    },
    _calculatePL: function(gKey, holdingId, calOptions){
        var id = holdingId;
        var needCalculate = calOptions ||
        {
            'P/L': true,
            'P/Lpc': true,
            'MarketValue': true,
            'YesterdayMarketValue': true
        };
        
        //single calculate
        var hd = this._getHoldingByHoldId(this.config.currentId, holdingId);
        var PL = 0, Cost = 0, MV = 0, yMv = 0,numofshares=parseFloat(hd["NumOfShares"]),
            costbasis=parseFloat(hd["CostBasis"]),comm=parseFloat(hd["Comm"]),purchaseprice=parseFloat(hd["PurchasePrice"]);
        if (hd && numofshares&&numofshares>0) {
            //mv
            var datalist=push.proxy.getMarketQuoteData(gKey);
            if (needCalculate['MarketValue']&&datalist["LastPrice"].value) {
                MV = numofshares * datalist["LastPrice"].value;
            }
            if (needCalculate['YesterdayMarketValue']&&datalist["ClosePrice"].value) {
                yMv = numofshares * datalist["ClosePrice"].value;
            }
            //p/l p/l%
            if (needCalculate['P/L'] || needCalculate['P/Lpc']) {
                if (!isNaN(costbasis)&&datalist["LastPrice"].value>=0) { //for .com portfolio
                    PL = hd["P/L"] = numofshares *
                    (datalist["LastPrice"].value -
                    costbasis);
                    Cost = numofshares * costbasis;
                }
                else if (!isNaN(purchaseprice) && !isNaN(comm) &&datalist["LastPrice"].value) { //for PMS portfolio
                        PL = hd["P/L"] = numofshares *
                        (datalist["LastPrice"].value -
                        (purchaseprice + (comm / numofshares)));
                        Cost = numofshares * purchaseprice + comm;
                }
            }
            
        }
        return {
            'P/L': PL,
            'P/Lpc': Cost > 0? PL / Cost * 100: '',
            'MarketValue':MV,
            'YesterdayMarketValue': yMv
        };
    },
    _updatePortfolioDataDOM: function(gKey, holdingId){
        var result = this._calculatePL(gKey, holdingId)
        var securitytype=gData[gKey]["type"];
        if (this.grid.DOMMap[holdingId]['MarketValue']) {
            this.grid.DOMMap[holdingId]['MarketValue'][this._textKey] = formatDoubledata(result['MarketValue'],securitytype);
        }
        if (this.grid.DOMMap[holdingId]['P/L']) {
            this.grid.DOMMap[holdingId]['P/L'][this._textKey] =formatDoubledata(result['P/L'],securitytype);
        }
        if (this.grid.DOMMap[holdingId]['P/Lpc']) {
            this.grid.DOMMap[holdingId]['P/Lpc'][this._textKey] = formatDoubledata(result['P/Lpc'],securitytype);
        }
        
    },
    _reBindTickers:function(gkeyList,failedTickers,holdings, showAlert){ //
        this.visibleRows = {}; // clear old visible rows
        var data = [], d = {}, addedHoldings = {}, invalidHoldIds = [];
        var rowLength = 0;
        for (var j = 0; j < holdings.length; j++) {
            if(this._checkTickerLength(rowLength,showAlert)) break;
            if(this.config.showInvalidTicker&&failedTickers&&failedTickers.length){
               for (var i = 0, l = failedTickers.length; i < l; i++) {  // add invalid tickers into watchlist
                   if (holdings[j].SecId.toUpperCase() == failedTickers[i].toUpperCase() || holdings[j].Ticker.toUpperCase() == failedTickers[i].toUpperCase()) {
					if (addedHoldings[holdings[j].HldgId]) {
						continue; // remove duplicate holding
					}
					if(this._checkTickerLength(rowLength,showAlert)) break;  // more than the maxmum of tickers.
					d = {
						'InvalidTicker': true,
						'HldgId':holdings[j].HldgId,
						'id': holdings[j].HldgId,
						'gkey': holdings[j].SecId,
						'SecId': holdings[j].SecId,
						'Symbol': holdings[j].Ticker.length == 0 ? failedTickers[i] : holdings[j].Ticker
					};
					//add grouping data
					if (this.config.groupBy) {//invalid holdings don't gData data
						var holdingData = this._getHoldingByHoldId(this.config.currentId, holdings[j].HldgId);
						d[this.config.groupBy] = holdingData ? holdingData[this.config.groupBy] : '';
					}
					data.push(d);
					addedHoldings[holdings[j].HldgId] = true;
					invalidHoldIds.push(holdings[j].HldgId);
					rowLength++;
					break;
				}
			}
		}
		if(gkeyList&&gkeyList.length){
			for (var i = 0; i < gkeyList.length; i++) {
				if (holdings[j].SecId == gData[gkeyList[i]]["performanceId"] || holdings[j].SecId == gData[gkeyList[i]]["secId"]) {
					if (addedHoldings[holdings[j].HldgId]) {
							continue; // remove duplicate holding
						}
						if(this._checkTickerLength(rowLength,showAlert)) break;  // more than the maxmum of tickers.
						d = {
							'id': holdings[j].HldgId,
							'HldgId':holdings[j].HldgId,//for rebindticker
							'gkey': gkeyList[i],
							'SecId': holdings[j].SecId,//for rebindticker
							'Symbol': gData[gkeyList[i]].ticker
						};
						//add grouping data
						 if (this.config.groupBy) {
							d[this.config.groupBy] = this._getGroupData(gkeyList[i],d.id);
						}
						data.push(d);
						addedHoldings[holdings[j].HldgId] = true;
						rowLength++;
					}
				}
			}  
        }
        if (typeof QS_watchlistDataManager != 'undefined') {
            QS_watchlistDataManager.createHoldingMap(this.config.currentId);
        }else{
            this.holdings=data;
        }
        this.grid.reBindData(data);
        if(this.config.showInvalidTicker){
            this._addInvalidFlag(invalidHoldIds);
        } 
        this._cacheGkeyList();
        if (!this.config.subscribeInvisibleTickers) {
            this._cacheVisibleRows();
            this.bindData(); //get data from gData for visible rows.
        }else{
             for(var i=0;i<gkeyList.length;i++){
                this.bindDataRow(gkeyList[i],false);
            }
        }
    },
	_addInvalidFlag: function(holdIds){
		for (var i = 0, l = holdIds.length; i < l; i++) {
			if (this.grid.DOMMap[holdIds[i]]['Symbol']) {
				if (this.grid.rows[holdIds[i]].data.InvalidTicker) {
					var dom = this.grid.DOMMap[holdIds[i]]['Symbol'];
					var el = $(dom);
					el.addClass('invalid-symbol');
					dom.innerHTML = '<a href="javascript:void(0)" title="Invalid ticker">' + el.html() + '</a>';
				}
			}
		}
    },
    _cacheGkeyList: function(){
        var gkeylist = [];
        this.failedTickers = [];
        var rows = this.grid.getRows(),pid=null;
        for (var r in rows) {
            pid= rows[r].data.pid||rows[r].data.SecId||rows[r].data.gkey;
            if (rows[r].data.InvalidTicker) {
                if (pid && $.inArray(pid, this.failedTickers) < 0) {
                    this.failedTickers.push(pid);
                }
            }
            else {
                if ($.inArray(rows[r].data.gkey, gkeylist) < 0) {
                    gkeylist.push(rows[r].data.gkey);
                }
            }
        }
        this.gkeyList = gkeylist;
    },
    //get visible rows/tickers and store into this.visibleRows, should be called on scroll/resize/rebind...
    _cacheVisibleRows: function(isDelay, callback){
        var self = this;
        var checkSubscribe = function(){
            var newVisibleRows = self.config.subscribeInvisibleTickers ? self.grid.rows : self.grid.getVisibleRow();
            var needSubscribeTicker = [];
            var needUnSubscribeTicker = [];
            var needToCheckTicker = [], inx;
            for (var r in newVisibleRows) {
                if (!self.visibleRows[r]) {
                    if (gData[newVisibleRows[r].data.gkey] && !newVisibleRows[r].data.InvalidTicker) {
                        needSubscribeTicker.push(newVisibleRows[r].data.gkey);
                    }
                    else {
                        //var inx = $.inArray(newVisibleRows[r].id, self.gKeyList)
                        //if(inx >= 0)needToCheckTicker.push(self.symbols[inx]);
                        if (!newVisibleRows[r].data.InvalidTicker) {
                            needToCheckTicker.push(newVisibleRows[r].data.pid);
                        }
                    }
                }
            }
            for (var r in self.visibleRows) {
                if (!newVisibleRows[r]&&self.visibleRows[r].data) {
                    needUnSubscribeTicker.push(self.visibleRows[r].data.gkey);
                }
            }
            if (needToCheckTicker.length) {
                self.tValidation.showAlert = false;
               // self.tValidation.checkTickers(needToCheckTicker, null, true);
            }
            self.tValidation.subscribe(needSubscribeTicker);
            self.tValidation.unSubscribe(needUnSubscribeTicker, true); // not delete the item in "gData".
            self.visibleRows = newVisibleRows;
        }
        if (isDelay) {
            //refresh visible rows, sync for datagrid resize
            this._cacheVisibleRowsTimer = 0;
            clearTimeout(this._cacheVisibleRowsTimer);
            this._cacheVisibleRowsTimer = setTimeout(function(){
                checkSubscribe();
                if (callback) {
                    callback();
                }
            }, 100);
        }
        else {
            checkSubscribe();
        }
    },
    stopSubscribe: function(){  // unsubscribe tickers.
        var tickers = [];
        var rows = this.visibleRows;
        for (var i in rows) {
            if(rows[i].data.InvalidTicker){continue;}
            tickers.push(gData[rows[i].data.gkey].performanceId);
        }
        this.tValidation.unSubscribe(tickers, true); // not delete the item in "gData".
    },
    show:function(){  // show grid.
        this.el.show();
    },
    hide:function(){
        this.el.hide();
    }
}
